iT邦幫忙

2023 iThome 鐵人賽

DAY 4
1
Software Development

Python十翼:與未來的自己對話系列 第 4

[Day04] 初翼 - Tips:11~15

  • 分享至 

  • xImage
  •  

今天我們繼續分享tips 11~15

11. set的應用:在iterable_a裡卻不在iterable_b裡

當想尋找在a裡卻不在b裡的元素時,一般會寫成# 11a。如果iterable_a是有序的且順序需要維持的,或許這是個不錯的方法。

# 11a
iterable_a = range(1, 5)
iterable_b = range(3, 10)

if __name__ == '__main__':
    result = [a
              for a in iterable_a
              if a not in iterable_b]
    print(f'{result=}')  # result=[1, 2]   

如果iterable_b的數量很大,或是有重複的元素,# 11a可以改成# 11b,這麼一來in的速度可以加快,也不會有重複的元素。

# 11b
iterable_a = range(1, 5)
iterable_b = range(3, 10)

if __name__ == '__main__':
    result = [a
              for a in iterable_a
              if a not in set(iterable_b)]
    print(f'{result=}')  # result=[1, 2]

不過大多數情況是,我們不在意兩邊的元素順序及是否有重複元素,這時可以直接用兩個set相減,如# 11c

# 11c
iterable_a = range(1, 5)
iterable_b = range(3, 10)


if __name__ == '__main__':
    result = set(iterable_a) - set(iterable_b)
    print(f'{result=}')  # result={1, 2}

12. set的應用:if後接多個條件

當想找出一個集合內,其值為某幾個數時,一般會寫成# 12a。如果if後接的條件只有少數幾個,我們會覺得這是個ok的寫法。

# 12a
iterable = range(20)

if __name__ == '__main__':
    result = [item
              for item in iterable
              if item == 3 or item == 7 or item == 15]
    print(f'{result=}')  # result=[3, 7, 15]

但是如果條件比較多的話,我們會試著看看能不能改成# 12b的寫法。這麼一來,程式看起來就不會太冗長,而且in wanted會比多個if更有效率。

# 12b
iterable = range(20)

if __name__ == '__main__':
    wanted = {3, 7, 15}
    result = [item
              for item in iterable
              if item in wanted]
    print(f'{result=}')  # result=[3, 7, 15]

13. types模組

在比較舊版本的Python,當想要取得有些物件的type時,需要先對該物件使用type。例如想取得Nonetype,必須這麼做:

# 13
none_type1 = type(None)

雖然這方式可行,但總覺得好像有點旁門左道。這就像您會直接使用int,而不會使用像int_type = type(1)的語法來取得int這個type一樣。

新版Python的types模組,支援了各式各樣的type,讓我們可以直接取用。現在如果想要取得Nonetype,建議改成這麼做:

# 13
import types

none_type2 = types.NoneType

我們可以驗證兩種方法是一樣的:

# 13
print(none_type1 is none_type2)  # True

14. collections.defaultdict vs dict.setdefault

當我們有很多筆資料想要整理成dict,但其中作為key的元素,卻可能重複時,我們一般會希望將value作為一個container,收集該key的每筆資訊。

# 14a是一個常見的寫法,但是我們覺得這樣的寫法不太好。原因是當if name not in dict1時,總共做了兩件事,建立[]以及append value,而在else裡,只做append value。既然append value是兩邊共同會做的事,為何不抽出來呢?

# 14a
data = [('a', 1), ('b', 2), ('c', 3), ('a', 4)]

if __name__ == '__main__':
    d = {}
    for name, value in data:
        if name not in d:
            d[name] = [value]
        else:
            d[name].append(value)

所以我們會傾向# 14b這樣的寫法。

# 14b
data = [('a', 1), ('b', 2), ('c', 3), ('a', 4)]

if __name__ == '__main__':
    d = {}
    for name, value in data:
        if name not in d:
            d[name] = []
        d[name].append(value)

此外Python的collections.defaultdict似乎更適合處理這類問題。# 14c也是屬於常見的作法。

# 14c
from collections import defaultdict

data = [('a', 1), ('b', 2), ('c', 3), ('a', 4)]

if __name__ == '__main__':
    d = defaultdict(list)
    for name, value in data:
        d[name].append(value)

最後,我們想分享一個比較冷門的寫法,就是直接使用dict.setdefault,如# 14d。第一次用可能會覺得有點怪。但是寫久了之後,發現相比於defaultdict要先import,再產生defaultdict instance才能使用,直接生成dict然後連續兩個.(dot)好像更一氣呵成。

# 14d
data = [('a', 1), ('b', 2), ('c', 3), ('a', 4)]

if __name__ == '__main__':
    d = {}
    for name, value in data:
        d.setdefault(name, []).append(value)

15. getattr適用時機

Python提供了非常方便來取得attribute的方法,就是我們習慣的.(dot)。但是當這個attribute名字是動態取得的,也就是無法在使用.(dot)前就知道時,就必須使用getattr

一個最常見的應用就是實作descriptor# 15a中,__set_name__會自動傳遞使用者於class中命名此NonDataDesc instance的名字,這麼一來這個名字是動態產生的,我們必須使用getattr,而無法使用類似instance.self._name語法來取值。

# 15a
class NonDataDesc:
    def __get__(self, instance, owner_cls):
        if instance is None:
            return self
        return getattr(instance, self._name)

    def __set_name__(self, owner_cls, name):
        self._name = f'_{name}'

或許您會覺得這只是因為instance.self._name中有兩個.(dot),但事實上,即便用一個中間變數來取代self._name也不會成功。# 15b中我們先將self._name指給一個變數my_name,然後返回instance.my_name。這是一個錯誤的descriptor實作,因為instance.my_name只是請Python幫我們去找my_name這個attribute,而不是動態由使用者給定的名字。

# 15b WRONG CODE!!!
class NonDataDesc:
    def __get__(self, instance, owner_cls):
        if instance is None:
            return self
        my_name = self._name 
        return instance.my_name

    def __set_name__(self, owner_cls, name):
        self._name = f'_{name}'

此外,getattr可以指定第三個參數,為找不到attribute時的回傳值。

Code

本日程式碼傳送門


上一篇
[Day03] 初翼 - Tips:6~10
下一篇
[Day05] 次翼 - Decorator:@func to func
系列文
Python十翼:與未來的自己對話30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言